package com.ihr360.expr.parser;

import com.ihr360.expr.ExprArray;
import com.ihr360.expr.ExprDouble;
import com.ihr360.expr.ExprException;
import com.ihr360.expr.ExprExpression;
import com.ihr360.expr.ExprInteger;
import com.ihr360.expr.ExprMissing;
import com.ihr360.expr.ExprPower;
import com.ihr360.expr.ExprString;
import com.ihr360.expr.ExprStringConcat;
import com.ihr360.expr.core.Expr;
import com.ihr360.expr.core.ExprFunction;
import com.ihr360.expr.core.ExprVariable;
import com.ihr360.expr.core.IBinaryOperator;
import com.ihr360.expr.operator.ExprAddition;
import com.ihr360.expr.operator.ExprDivision;
import com.ihr360.expr.operator.ExprEqual;
import com.ihr360.expr.operator.ExprGreaterThan;
import com.ihr360.expr.operator.ExprGreaterThanOrEqualTo;
import com.ihr360.expr.operator.ExprLessThan;
import com.ihr360.expr.operator.ExprLessThanOrEqualTo;
import com.ihr360.expr.operator.ExprMultiplication;
import com.ihr360.expr.operator.ExprNotEqual;
import com.ihr360.expr.operator.ExprSubtraction;

import java.io.IOException;
import java.util.ArrayList;

/**
 * 表达式解析器
 */
public class ExprParser {
    private Expr current;
    private IParserVisitor visitor;
    public static Expr parse(String text) throws IOException, ExprException {
        ExprParser p = new ExprParser();
        p.parse(new ExprLexer(text));
        return p.get();
    }
    public void setParserVisitor(IParserVisitor visitor) {
        this.visitor = visitor;
    }

    public void parse(ExprLexer lexer) throws IOException, ExprException {
        ExprToken e = null;
        while ((e = lexer.next()) != null) {
            parseToken(lexer, e);
        }
    }

    private void parseToken(ExprLexer lexer,ExprToken token)  throws ExprException,IOException{
        switch (token.type){
            case Plus:
            case Minus:
            case Multiply:
            case Divide:
            case Power:
            case StringConcat:
            case LessThan:
            case LessThanOrEqualTo:
            case GreaterThan:
            case GreaterThanOrEqualTo:
            case Equal:
            case NotEqual:
                parseOperator(token);
                break;
            case Decimal:
            case Integer:
            case String:
            case Variable:
                parseValue(token);
                break;
            case OpenBracket:
                parseExpression(lexer);
                break;
            case Function:
                parseFunction(token, lexer);
                break;
            case OpenBrace:
                parseArray(lexer);
                break;
            default:
                throw new ExprException("Unexpected " + token.type + " found");
        }
    }

    private void parseValue(ExprToken e) throws ExprException {
        Expr value = null;
        switch (e.type) {
            case Decimal:
                value = new ExprDouble(e.doubleValue);
                break;
            case Integer:
                value = new ExprInteger(e.integerValue);
                break;
            case String:
                value = new ExprString(e.val);
                break;
            case Variable:
                value = new ExprVariable(e.val);
                if (visitor != null){
                    visitor.annotateVariable((ExprVariable) value);
                }
                break;
        }
        setValue(value);
    }

    private void setValue(Expr value) throws ExprException {
        if (current == null) {
            current = value;
            return;
        } else {
            Expr c = current;
            do {
                if (!(c instanceof IBinaryOperator)){
                    throw new ExprException("Expected operator not found");
                }
                Expr rhs = ((IBinaryOperator) c).getRHS();
                if (rhs == null) {
                    ((IBinaryOperator) c).setRHS(value);
                    return;
                } else {
                    c = rhs;
                }
            } while (c != null);

            throw new ExprException("Unexpected token found");
        }
    }

    private void parseOperator(ExprToken e) throws ExprException {
        switch (e.type) {
            case Plus:
                Expr lhs = current;
                current = new ExprAddition(lhs, null);
                break;
            case Minus:
                lhs = current;
                current = new ExprSubtraction(lhs, null);
                break;
            case Multiply:
                parseMultiplyDivide(new ExprMultiplication(null, null));
                break;
            case Divide:
                parseMultiplyDivide(new ExprDivision(null, null));
                break;
            case Power:
                parseMultiplyDivide(new ExprPower(null, null));
                break;
            case StringConcat:
                parseMultiplyDivide(new ExprStringConcat(null, null));
                break;
            case LessThan:
                current = new ExprLessThan(current, null);
                break;
            case LessThanOrEqualTo:
                current = new ExprLessThanOrEqualTo(current, null);
                break;
            case GreaterThan:
                current = new ExprGreaterThan(current, null);
                break;
            case GreaterThanOrEqualTo:
                current = new ExprGreaterThanOrEqualTo(current, null);
                break;
            case NotEqual:
                current = new ExprNotEqual(current, null);
                break;
            case Equal:
                current = new ExprEqual(current, null);
                break;
            default:
                throw new ExprException("Unhandled operator type: " + e.type);
        }
    }

    private void parseMultiplyDivide(IBinaryOperator md) throws ExprException {
        if (current == null){
            throw new ExprException("Unexpected null token");
        }

        Expr c = current;
        Expr prev = null;
        while (c != null) {
            if (c instanceof ExprAddition || c instanceof ExprSubtraction) {
                prev = c;
                c = ((IBinaryOperator) c).getRHS();
            } else {
                if (prev == null) {
                    md.setLHS(current);
                    current = (Expr) md;
                    break;
                } else {
                    IBinaryOperator b = (IBinaryOperator) prev;
                    md.setLHS(b.getRHS());
                    b.setRHS((Expr) md);
                    break;
                }
            }
        }
    }

    private void parseExpression(ExprLexer lexer) throws IOException,
            ExprException {
        Expr c = current;
        current = null;
        ExprToken e = null;
        while ((e = lexer.next()) != null) {
            if (e.type.equals(ExprTokenType.CloseBracket)) {
                Expr t = current;
                current = c;
                setValue(new ExprExpression(t));
                break;
            } else {
                parseToken(lexer, e);
            }
        }
    }

    private void parseFunction(ExprToken token, ExprLexer lexer)
            throws ExprException, IOException {
        Expr c = current;
        current = null;
        ExprToken e = null;
        ArrayList args = new ArrayList();
        while ((e = lexer.next()) != null) {
            if (e.type.equals(ExprTokenType.Comma)) {
                if (current == null){
                    args.add(ExprMissing.MISSING);
                }
                else{
                    args.add(current);
                }
                current = null;
            } else if (e.type.equals(ExprTokenType.CloseBracket)) {
                if (current != null){
                    args.add(current);
                }
                current = c;
                break;
            } else {
                parseToken(lexer, e);
            }
        }

        ExprFunction f = new ExprFunction(token.val, (Expr[]) args
                .toArray(new Expr[0]));

        if (visitor != null){
            visitor.annotateFunction(f);
        }
        setValue(f);
    }

    private void parseArray(ExprLexer lexer) throws ExprException, IOException {
        Expr c = current;
        current = null;
        ExprToken e = null;
        int cols = -1;
        int count = 0;
        ArrayList args = new ArrayList();
        while ((e = lexer.next()) != null) {
            if (e.type.equals(ExprTokenType.Comma)) {
                if (current == null)
                    throw new ExprException(
                            "Arrays cannot contain empty values");
                else
                    args.add(current);
                current = null;
                count++;
            } else if (e.type.equals(ExprTokenType.SemiColon)) {
                if (current == null)
                    throw new ExprException(
                            "Arrays cannot contain empty values");
                else
                    args.add(current);
                current = null;
                count++;

                if (count == 0) {
                    throw new ExprException(
                            "Array rows must contain at least one element");
                }
                if (cols != -1 && count != cols) {
                    throw new ExprException("Array rows must be equal sizes");
                }

                cols = count;
                count = 0;
            } else if (e.type.equals(ExprTokenType.CloseBrace)) {
                if (current != null)
                    args.add(current);
                current = c;
                break;
            } else {
                parseToken(lexer, e);
            }
        }

        int rows = 1;
        if (cols == -1)
            cols = args.size();
        else
            rows = args.size() / cols;
        ExprArray a = new ExprArray(rows, cols);
        for (int i = 0; i < args.size(); i++) {
            a.set(0, i, (Expr) args.get(i));
        }

        setValue(a);
    }

    public Expr get() {
        return current;
    }
}